/**
 * @author Shen Yi - bm2736892@gmail.com
 */

/**
 * 
 * @param {String} name
 * @param {Object} pack
 * @param {Object} client
 * @param {Object} o3d
 * @param {Object} option {position, color, type, attenuation, power, !target, !aspect, !direction}
 */
var Light = function(name, pack, client, o3d, option){
	//=========================public property=====================
	this.name = name;
	//the shadow is genereated in the deferred shading step
	this.shadow = null;
	
	//=========================private property====================
	this._pack = pack;
	this._client = client;
	this._o3d = o3d;
	this._math = o3djs.math;
	this._matrix4 = this._math.matrix4;
	
	this._camera = null;
	
	this._shadowMap = null;
	this._shadowMapSampler = null;
	this._shadowMapSize = 2048;
	
	//TODO: implement the point and direct light
	this._type = isset(option.type) ? option.type : "POINT";		//SPOT, POINT, DIRECT
	if(this._type != "DIRECT"){
		this._position = isset(option.position) ? option.position : [0, 0, 0];
		this._attenuation = isset(option.attenuation) ? option.attenuation : 0 	//[0, oo], better [0, 1]
	}
	this._color = isset(option.color) ? option.color : [1, 1, 1, 1];
	this._power = isset(option.power) ? option.power : 1;
	if(this._type == "SPOT"){
		this._target = isset(option.target) ? option.target : [0, 0, 0];
		this._aspect = isset(option.aspect) ? option.aspect : 45;
	}
	if(this._type == "DIRECT"){
		this._direction = isset(option.direction) ? option.direction : [0, 1, 0];
	}
}

Light.prototype = {
	getPosition : function(){
		return this._position;
	},
	getColor : function(){
		return this._color;
	},
	getType : function(){
		return this._type;
	},
	getAttenuation : function(){
		return this._attenuation
	},
	getPower : function(){
		return this._power;
	},
	getTarget : function(){
		return this._target;
	},
	getAspect : function(){
		return this._aspect;
	},
	getDirection : function(){
		return this._direction;
	},
	getShadowMap : function(){
		return this._shadowMap;
	},
	getShadowMapSampler : function(){
		return this._shadowMapSampler;
	},
	getCamera : function(){
		return this._camera;
	},
	getLightViewProjection : function(){
		return this._matrix4.mul(this._camera.getCameraMatrix(), this._camera.getProjectionMatrix());
	},
	/**
	 * 
	 * @param {Float3} position
	 */
	setPosition : function(position){
		this._position = position;
	},
	/**
	 * 
	 * @param {Float4} color
	 */
	setColor : function(color){
		this._color = color;
	},
	/**
	 * 
	 * @param {String} type SPOT, POINT, AREA
	 */
	setType : function(type){
		this._type = type;
	},
	/**
	 * 
	 * @param {Float} attenuation 
	 */
	setAttenuation : function(attenuation){
		this._attenuation = attenuation;
	},
	/**
	 * 
	 * @param {Float} power
	 */
	setPower : function(power){
		this._power = power;
	},
	/**
	 * 
	 * @param {Float3} target
	 */
	setTarget : function(target){
		if(this._type == "SPOT"){
			this._target = option.target;
		}else{
			error(this._type + " light has target option");
		}
	},
	/**
	 * 
	 * @param {Float} aspect
	 */
	setAspect : function(aspect){
		if(this._type == "SPOT"){
			this._aspect = option.aspect;
		}else{
			error(this._type + " light has aspect option");
		}
	},
	/**
	 * 
	 * @param {String} transformParentName
	 */
	createShadowMap : function(transformParentName){
		var transform = this._pack.createObject("Transform");
		//insert the transform between the transform and the childrent
		transformParent = SceneManager.getTransform(transformParentName);
		var children = transformParent.children;
		for(var i = 0; i < children.length; i++){
			children[i].parent = transform;	
		}
		transform.parent = transformParent;
		
		//create a render node
		var renderNode = this._pack.createObject("RenderNode");
		renderNode.priority = 0;
		renderNode.parent = this._client.renderGraphRoot;
		
		//create a shadow map sampler
		this._shadowMap = TextureManager.createRenderTarget2D("shadow_map_" + this.name, this._shadowMapSize, this._shadowMapSize, "ABGR32F");
		this._shadowMapSampler = TextureManager.createSampler("shadow_map_" + this.name, {
			"minFilter" : "POINT",
			"magFilter" : "POINT",
			"mipFilter" : "POINT",
			"addressModeU" : "BORDER",
			"addressModeV" : "BORDER"
		})
		this._shadowMapSampler.texture = this._shadowMap;
		
		//create a new view 
		this._camera = new Camera(this.name, this._pack, this._client, this._o3d, transform, renderNode, this._shadowMap.name);
		if(this._type == "DIRECT"){
			//TODO: change to orthographic
			this._camera.setOrthographic(-500, 500, -500, 500, 0, 2000);
			this._camera.setCamera(this._math.mulScalarVector(1000, this._math.normalize(this._direction)), [0, 0, 0], [0, 1, 0]);
		}
		else{
			//TODO: the up should be computed instead of fixed
			this._camera.setCamera(this._position, this._target, [0, 1, 0]);
			this._camera.setProjection(this._aspect, 1, 1, 5000);
		}
		
		//create a material and create a draw element for every elements in the transform
		var material = MaterialManager.createMaterialFromShader("shadow_map", "media/shaders/shadowMap.shader");
		material.drawList = this._camera.getViewInfo().performanceDrawList;
		transform.createDrawElements(this._pack, material);
	},
	/**
	 * bind the light to material so the light param in the shader will be same as 
	 * the light param
	 * @param {String} materialName
	 */
	bindToMaterial : function(materialName){
		var options = [];
		if(isset(materialName)){
			options[materialName] = MaterialManager.getOption(materialName);
		}else{
			options = MaterialManager.getOption();
		}
		for(var materialName in options){
			MaterialManager.updateParam(materialName, "lightWorldPos", this._position);
			MaterialManager.updateParam(materialName, "diffuse", this._color);
			MaterialManager.updateParam(materialName, "specular", this._color);
			//MaterialManager.updateParam(materialName, "shadow_map", this._shadowMapSampler);
			//TODO: add the attenuation in the shader
			//MaterialManager.updateParam(materialName, "attenuation", this._attenuation);
			if(this._camera){
				MaterialManager.updateParam(materialName, "lightViewProjection", this._matrix4.composition(this._camera.getProjectionMatrix(), this._camera.getCameraMatrix()))
			}
		}
	},
	/**
	 * 
	 * @param {String} transformName
	 */
	bindToTransform : function(transformName){
		var primitives = SceneManager.getTransformPrimitiveMap(transformName);
		for(var i = 0; i < primitives.length; i++){
			var materialName = SceneManager.getPrimitive(primitives[i]).material.name;
			if(isset(MaterialManager.getOption(materialName))){
				this.bindToMaterial(materialName);
				//only for the editor
				this.bindToMaterial(materialName + "_highlight");
			}
		}
	},
	/**
	 * TODO: enable the shadow of the primitive
	 * @param {String} primitiveName
	 */
	addShadow : function(primitiveName){
		
	},
	removeShadow : function(){
		
	}
}
